package net.phoenix.repository.property;

import java.io.InputStream;
import java.math.BigDecimal;
import java.util.Calendar;

import javax.jcr.AccessDeniedException;
import javax.jcr.Binary;
import javax.jcr.InvalidItemStateException;
import javax.jcr.Item;
import javax.jcr.ItemExistsException;
import javax.jcr.ItemNotFoundException;
import javax.jcr.ItemVisitor;
import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.PropertyType;
import javax.jcr.ReferentialIntegrityException;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.ValueFactory;
import javax.jcr.ValueFormatException;
import javax.jcr.lock.LockException;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.nodetype.NoSuchNodeTypeException;
import javax.jcr.nodetype.PropertyDefinition;
import javax.jcr.version.VersionException;

import net.phoenix.repository.BasicNode;
import net.phoenix.repository.BasicRepository;
import net.phoenix.repository.JCRUtils;

/**
 * 
 * Jigsaw server
 * 
 * @author <a href="shamphone@gmail.com">Li XiongFeng</a>
 * @date 2012-1-3
 * @version 1.0.0
 */
public abstract class BasicProperty implements Property {
	protected BasicRepository repository;
	protected PropertyDefinition definition;
	protected Node node;
	protected Value[] values;
	protected boolean modified;

	/**
	 * create single value property
	 * @param node
	 * @param definition
	 * @param value
	 */
	public BasicProperty(BasicNode node, PropertyDefinition definition, Value value) {
		this.node = node;
		this.definition = definition;
		this.modified = false;
		if(value!=null)
			this.values = new Value[]{value};
		else
			this.values = new Value[0];
	}
	/**
	 * create multiple values property
	 * @param node
	 * @param definition
	 * @param values
	 */
	public BasicProperty(BasicNode node, PropertyDefinition definition, Value[] values) {
		this.node = node;
		this.definition = definition;
		this.modified = false;
		this.values = values;
	}
	
	/**
	 * create a property with default value;
	 * @param node
	 * @param definition
	 */
	public BasicProperty(BasicNode node, PropertyDefinition definition) {
		this.node = node;
		this.definition = definition;
		this.modified = true;
		this.values = definition.getDefaultValues();
		
	}
	
	/**
	 * create property with given types;
	 * @param node
	 * @param definition
	 * @return
	 */
	public static BasicProperty getInstance(BasicNode node, PropertyDefinition definition){
		BasicProperty property = null;
		switch (definition.getRequiredType()) {
		case PropertyType.BINARY:
			property = new BinaryProperty(node,definition);
			break;
		case PropertyType.BOOLEAN:
			property = new BooleanProperty(node,definition);
			break;
		case PropertyType.DATE:
			property = new DateProperty(node,definition);
			break;
		case PropertyType.DOUBLE:
			property = new DoubleProperty(node,definition);
			break;
		case PropertyType.LONG:
			property = new LongProperty(node,definition);
			break;
		case PropertyType.PATH:
			property = new PathProperty(node,definition);
			break;
		case PropertyType.NAME:
			property = new NameProperty(node,definition);
			break;
		case PropertyType.REFERENCE:
			property = new ReferenceProperty(node,definition);
			break;
		default:
			property = new StringProperty(node,definition);
		}
		return property;
	}
	
	/**
	 * create property with given types;
	 * @param node
	 * @param definition
	 * @return
	 */
	public static BasicProperty getInstance(BasicNode node, PropertyDefinition definition, Value[] values){
		BasicProperty property = null;
		switch (definition.getRequiredType()) {
		case PropertyType.BINARY:
			property = new BinaryProperty(node,definition,values);
			break;
		case PropertyType.BOOLEAN:
			property = new BooleanProperty(node,definition,values);
			break;
		case PropertyType.DATE:
			property = new DateProperty(node,definition,values);
			break;
		case PropertyType.DOUBLE:
			property = new DoubleProperty(node,definition,values);
			break;
		case PropertyType.LONG:
			property = new LongProperty(node,definition,values);
			break;
		case PropertyType.PATH:
			property = new PathProperty(node,definition,values);
			break;
		case PropertyType.NAME:
			property = new NameProperty(node,definition,values);
			break;
		case PropertyType.REFERENCE:
			property = new ReferenceProperty(node,definition,values);
			break;
		default:
			property = new StringProperty(node,definition,values);
		}
		return property;
	}
	
	/**
	 * create property with given types;
	 * @param node
	 * @param definition
	 * @return
	 */
	public static BasicProperty getInstance(BasicNode node, PropertyDefinition definition, Value value){
		BasicProperty property = null;
		switch (definition.getRequiredType()) {
		case PropertyType.BINARY:
			property = new BinaryProperty(node,definition, value);
			break;
		case PropertyType.BOOLEAN:
			property = new BooleanProperty(node,definition,value);
			break;
		case PropertyType.DATE:
			property = new DateProperty(node,definition,value);
			break;
		case PropertyType.DOUBLE:
			property = new DoubleProperty(node,definition,value);
			break;
		case PropertyType.LONG:
			property = new LongProperty(node,definition,value);
			break;
		case PropertyType.PATH:
			property = new PathProperty(node,definition,value);
			break;
		case PropertyType.NAME:
			property = new NameProperty(node,definition,value);
			break;
		case PropertyType.REFERENCE:
			property = new ReferenceProperty(node,definition,value);
			break;
		default:
			property = new StringProperty(node,definition,value);
		}
		return property;
	}
	
	/**
	 * 
	 * @return
	 * @throws RepositoryException
	 */
	public boolean exists() throws RepositoryException{
		return this.values.length>0;
	}


	/**
	 * @return
	 * @throws RepositoryException
	 */
	protected ValueFactory getValueFactory() throws RepositoryException {
		return this.node.getSession().getValueFactory();
	}


	/**
	 * 
	 * @param values
	 *            PropertyValue 数据格式转换错误
	 * @throws ValueFormatException
	 */
	@Override
	public void setValue(Value[] values) throws ValueFormatException {
		if (values == null)
			this.values = new Value[0];
		else
			this.values = values;
		this.modified = true;
	}

	/**
	 * 
	 * @return PropertyValue
	 * @throws RepositoryException
	 */
	@Override
	public Value[] getValues() throws RepositoryException {
		return this.values;
	}

	/**
	 * 
	 * @return String[]
	 * @throws RepositoryException
	 */
	
	public String[] getArray() throws RepositoryException {
		return JCRUtils.toStringArray(this.values);
	}

	/**
	 * @return an <code>long</code>.
	 * 
	 * @return an <code>long</code>.
	 * @throws RepositoryException
	 * @throws IllegalStateException
	 * @throws ValueFormatException
	 */
	@Override
	public long getLength() throws ValueFormatException, IllegalStateException, RepositoryException {
		long length = 0;
		for (int i = 0; i < this.values.length; i++)
			length += this.values[i].getString().length();
		return length;
	}

	/**
	 * 
	 * @return an array of lengths
	 * @throws RepositoryException
	 * @throws IllegalStateException
	 * @throws ValueFormatException
	 */
	@Override
	public long[] getLengths() throws ValueFormatException, IllegalStateException, RepositoryException {
		long[] lengths = new long[this.values.length];
		for (int i = 0; i < this.values.length; i++)
			lengths[i] = this.values[i].getString().length();
		return lengths;
	}

	/**
	 * 
	 * @return PropertyValue
	 * @throws RepositoryException
	 */
	@Override
	public Value getValue() throws RepositoryException {
		if (this.values.length == 0)
			return null;
		return this.values[0];
	}

	/**
	 * 
	 * @param value
	 *            String
	 * @throws ValueFormatException
	 *             数据格式转换错误
	 */
	@Override
	public void setValue(String value) throws RepositoryException {
		this.setValue(this.getValueFactory().createValue(value, this.getType()));
		this.modified = true;

	}

	/**
	 * 
	 * @param value
	 *            Calendar
	 * @throws RepositoryException
	 *             数据格式转换错误
	 */
	@Override
	public void setValue(Calendar value) throws RepositoryException {
		throw new ValueFormatException("Could not convert from Calendar to "+ PropertyType.nameFromValue(this.getType())); 
	}

	/**
	 * 
	 * @param value
	 *            InputStream
	 * @throws RepositoryException
	 *             数据格式转换错误
	 */
	@Override
	public void setValue(Binary value) throws RepositoryException {
		throw new ValueFormatException("Could not convert from Binary to "+ PropertyType.nameFromValue(this.getType())); 
	}

	/**
	 * 
	 * @param value
	 *            InputStream
	 * @throws RepositoryException
	 *             数据格式转换错误
	 */
	@Override
	public void setValue(InputStream value) throws RepositoryException {
		throw new ValueFormatException("Could not convert from InputStream to "+ PropertyType.nameFromValue(this.getType())); 
	}

	/**
	 * 
	 * @param value
	 *            long
	 * @throws RepositoryException
	 *             数据格式转换错误
	 */
	@Override
	public void setValue(long value) throws RepositoryException {
		throw new ValueFormatException("Could not convert from long to "+ PropertyType.nameFromValue(this.getType())); 
	}

	/**
	 * 
	 * @param value
	 *            PropertyValue 数据格式转换错误
	 * @throws RepositoryException
	 */
	@Override
	public void setValue(Value value) throws RepositoryException {
		this.setValue(new Value[] { value });
		this.modified = true;		
	}

	/**
	 * 
	 * @param value
	 *            Content
	 * @throws RepositoryException
	 *             数据格式转换错误
	 */
	@Override
	public void setValue(Node value) throws RepositoryException {
		throw new ValueFormatException("Could not convert from Node to "+ PropertyType.nameFromValue(this.getType())); 
	}

	/**
	 * 
	 * @param values
	 *            String[]
	 * @throws RepositoryException
	 */
	@Override
	public void setValue(String[] strValues) throws RepositoryException {
		Value[] values = new Value[strValues.length];
		for(int i=0;i<strValues.length;i++) {
			values[i] = this.getValueFactory().createValue(strValues[i], this.getType());
		}
		this.values = values;
		this.modified = true;
	}

	/**
	 * 
	 * @param value
	 *            double
	 * @throws RepositoryException
	 *             数据格式转换错误
	 */
	@Override
	public void setValue(double value) throws RepositoryException {
		throw new ValueFormatException("Could not convert from double to "+ PropertyType.nameFromValue(this.getType())); 
	}

	/**
	 * 
	 * @param value
	 *            boolean
	 * @throws RepositoryException
	 *             数据格式转换错误
	 */
	@Override
	public void setValue(boolean value) throws RepositoryException {
		throw new ValueFormatException("Could not convert from boolean to "+ PropertyType.nameFromValue(this.getType())); 
	}

	/**
	 * 
	 * @return boolean
	 * @throws RepositoryException
	 */
	@Override
	public boolean getBoolean() throws RepositoryException {
		if (this.values.length == 0)
			return false;
		return this.getValue().getBoolean();
	}

	/**
	 * 
	 * @return Calendar
	 * @throws RepositoryException
	 */
	@Override
	public Calendar getDate() throws RepositoryException {
		if (this.values.length == 0)
			return null;
		return this.getValue().getDate();
	}

	/**
	 * 
	 * @return PropertyDefinition
	 */
	@Override
	public PropertyDefinition getDefinition() {
		return this.definition;
	}

	/**
	 * 
	 * @return double
	 * @throws RepositoryException
	 */
	@Override
	public double getDouble() throws RepositoryException {
		if (this.values.length == 0)
			return 0;
		return this.getValue().getDouble();
	}

	/**
	 * 
	 * @return long
	 * @throws RepositoryException
	 */
	@Override
	public long getLong() throws RepositoryException {
		if (this.values.length == 0)
			return 0l;
		return this.getValue().getLong();
	}

	/**
	 * 
	 * @return Content
	 */
	@Override
	public Node getParent() {
		return this.node;
	}

	/**
	 * 
	 * @return Content
	 * @throws RepositoryException
	 * @throws IllegalStateException
	 */
	@Override
	public Node getNode() throws IllegalStateException, RepositoryException {
		throw new ValueFormatException("Could not convert from "+ PropertyType.nameFromValue(this.getType())+" to Node"); 
	}

	/**
	 * 
	 * @return ContentRepository
	 * @throws RepositoryException
	 */
	public Repository getRepository() throws RepositoryException {
		return this.node.getSession().getRepository();
	}

	/**
	 * 
	 * @return InputStream
	 * @throws RepositoryException
	 */
	@Override
	@Deprecated
	public InputStream getStream() throws RepositoryException {
		if (this.values.length == 0)
			return null;
		return this.getValue().getStream();
	}

	/**
	 * 
	 * @return String
	 * @throws RepositoryException
	 * @throws IllegalStateException
	 */
	@Override
	public String getString() throws IllegalStateException, RepositoryException {
		StringBuffer buffer = new StringBuffer("");
		for(Value value: this.values){
			buffer.append(value.toString());
		}
		return buffer.toString();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.jcr.Property#getBinary()
	 */
	@Override
	public Binary getBinary() throws ValueFormatException, RepositoryException {
		if (this.values.length == 0)
			return null;
		return this.getValue().getBinary();
	}

	/**
	 * 
	 * @return int
	 */
	@Override
	public int getType() {
		return this.definition.getRequiredType();
	}

	/**
	 * 返回节点或者属性的深度，即从根节点到当前节点/属性的距离
	 * <ul>
	 * <li>根节点深度为 0 .
	 * <li>节点/属性的深度为其父节点的深度+1.
	 * </ul>
	 * 
	 * @return 节点或者属性的深度，即从根节点到当前节点/属性的距离.
	 * @throws RepositoryException
	 */
	@Override
	public int getDepth() throws RepositoryException {
		return this.getParent().getDepth() + 1;
	}

	/**
	 * 获取指定深度的父节点
	 * 
	 * @param depth
	 *            int 0<=depth<= getDepth()，0为根节点，以此类推
	 * @return Item
	 * @throws RepositoryException
	 * @throws ItemNotFoundException
	 * @throws AccessDeniedException
	 */
	@Override
	public Item getAncestor(int depth) throws AccessDeniedException, ItemNotFoundException, RepositoryException {
		int MaxDepth = this.getDepth();
		if ((depth < 0) || (depth > MaxDepth))
			return null;
		else {
			Item parent = this.getParent();
			while (--depth != 0)
				parent = parent.getParent();
			return parent;
		}
	}

	/**
	 * 名称，指在父节点下的名称
	 * 
	 * @return String
	 */
	@Override
	public String getName() {
		return this.definition.getName();
	}

	/**
	 * 属性路径
	 * 
	 * @return String
	 * @throws RepositoryException
	 */
	@Override
	public String getPath() throws RepositoryException {
		return this.node.getPath() + "/@" + this.getName();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		try {
			return this.getString();
		} catch (RepositoryException e) {
			return super.toString();
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.jcr.Item#getSession()
	 */
	@Override
	public Session getSession() throws RepositoryException {
		return this.node.getSession();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.jcr.Item#isNode()
	 */
	@Override
	public boolean isNode() {
		return false;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.jcr.Item#isNew()
	 */
	@Override
	public boolean isNew() {
		return false;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.jcr.Item#isModified()
	 */
	@Override
	public boolean isModified() {
		return this.modified;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.jcr.Item#isSame(javax.jcr.Item)
	 */
	@Override
	public boolean isSame(Item otherItem) throws RepositoryException {
		if (otherItem instanceof Property) {
			Property another = (Property) otherItem;
			return another.getNode().equals(node) && another.getName().equals(this.getName());
		}
		return false;
	}

	@Override
	public void accept(ItemVisitor visitor) throws RepositoryException {
	}


	
//	public void index(Document document) throws RepositoryException {
//		PropertyDefinition definition =  this.getDefinition();
//		if(definition.isFullTextSearchable()){
//			for(Value value: this.values){
//				if(value!=null && value.getString()!=null )
//				document.add(new Field(definition.getName(), value.getString(), Field.Store.YES, Field.Index.ANALYZED));
//			}
//		}
//	}
	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.jcr.Item#refresh(boolean)
	 */
	@Override
	public void refresh(boolean keepChanges) throws InvalidItemStateException, RepositoryException {
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.jcr.Item#remove()
	 */
	@Override
	public void remove() throws VersionException, LockException, ConstraintViolationException, AccessDeniedException, RepositoryException {
		this.values = new Value[0];
		this.modified = true;
	}

	@Override
	public void setValue(BigDecimal value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
		throw new ValueFormatException("Could not convert from BigDecimal to "+ PropertyType.nameFromValue(this.getType())); 
	}

	@Override
	public BigDecimal getDecimal() throws ValueFormatException, RepositoryException {
		throw new ValueFormatException("Could not convert from "+ PropertyType.nameFromValue(this.getType())+" to BigDecimal"); 
	}

	@Override
	public Property getProperty() throws ItemNotFoundException, ValueFormatException, RepositoryException {
		throw new ValueFormatException("Could not convert from "+ PropertyType.nameFromValue(this.getType())+" to Property"); 
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.jcr.Property#isMultiple()
	 */
	@Override
	public boolean isMultiple() throws RepositoryException {
		return this.definition.isMultiple();
	}
	
	@SuppressWarnings("deprecation")
	@Override
	public void save() throws AccessDeniedException, ItemExistsException, ConstraintViolationException, InvalidItemStateException, ReferentialIntegrityException, VersionException, LockException,
			NoSuchNodeTypeException, RepositoryException {
		this.node.save();
		
	}
}
